<template>
  <Select @dropdownVisibleChange="handleFetch" v-bind="$attrs" @change="handleChange" :options="getOptions" v-model:value="state">
    <template #[item]="data" v-for="item in Object.keys($slots)">
      <slot :name="item" v-bind="data || {}"></slot>
    </template>
    <template #suffixIcon v-if="loading">
      <LoadingOutlined spin />
    </template>
    <template #notFoundContent v-if="loading">
      <span>
        <LoadingOutlined spin class="mr-1" />
        {{ t('component.form.apiSelectNotFound') }}
      </span>
    </template>
  </Select>
</template>

<script lang="ts">
export default {
  name: 'ApiSelect',
  inheritAttrs: false,
};
</script>
<script lang="ts" setup>
import { PropType, ref, watchEffect, computed, unref, watch } from 'vue';
import { isFunction } from '/@/utils/is';
import { get, omit } from 'lodash-es';
import { Select } from 'ant-design-vue';
import { LoadingOutlined } from '@ant-design/icons-vue';
import { useI18n } from '/@/hooks/web/useI18n';
import { propTypes } from '/@/utils/propTypes';
import { useAttrs } from '/@/hooks/core/useAttrs';
import { useRuleFormItem } from '/@/hooks/component/useFormItem';

type OptionsItem = { label: string; value: string; disabled?: boolean };

const props = defineProps({
  value: [Array, Object, String, Number],
  numberToString: propTypes.bool,
  api: {
    type: Function as PropType<(arg?: Recordable) => Promise<OptionsItem[]>>,
    default: null,
  },
  // api params
  params: {
    type: Object as PropType<Recordable>,
    default: () => ({}),
  },
  // support xxx.xxx.xx
  resultField: propTypes.string.def(''),
  labelField: propTypes.string.def('label'),
  valueField: propTypes.string.def('value'),
  immediate: propTypes.bool.def(true),
});

const emits = defineEmits(['options-change', 'change']);
const options = ref<OptionsItem[]>([]);
const loading = ref(false);
const isFirstLoad = ref(true);
const emitData = ref<any[]>([]);
const attrs = useAttrs();
const { t } = useI18n();

// Embedded in the form, just use the hook binding to perform form verification
const [state] = useRuleFormItem(props, 'value', 'change', emitData);

const getOptions = computed(() => {
  const { labelField, valueField, numberToString } = props;

  return unref(options).reduce((prev, next: Recordable) => {
    if (next) {
      const value = next[valueField];
      prev.push({
        ...omit(next, [labelField, valueField]),
        label: next[labelField],
        value: numberToString ? `${value}` : value,
      });
    }
    return prev;
  }, [] as OptionsItem[]);
});

function emitChange() {
  emits('options-change', unref(getOptions));
}

async function fetch() {
  const api = props.api;
  if (!api || !isFunction(api)) return;
  options.value = [];
  try {
    loading.value = true;
    const res = await api(props.params);
    if (Array.isArray(res)) {
      options.value = res;
      emitChange();
      return;
    }
    if (props.resultField) {
      options.value = get(res, props.resultField) || [];
    }
    emitChange();
  } catch (error) {
    // eslint-disable-next-line no-console
    console.warn(error);
  } finally {
    loading.value = false;
  }
}

watchEffect(() => {
  props.immediate && fetch();
});

watch(
  () => props.params,
  () => {
    !unref(isFirstLoad) && fetch();
  },
  { deep: true }
);

async function handleFetch() {
  if (!props.immediate && unref(isFirstLoad)) {
    await fetch();
    isFirstLoad.value = false;
  }
}

function handleChange(_, ...args) {
  emitData.value = args;
}

defineExpose({
  state,
  attrs,
  getOptions,
  loading,
  t,
  handleFetch,
  handleChange,
});
</script>
